/*
 * Copyright (C) 2008-2009 by egnite GmbH
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * For additional information see http://www.ethernut.de/
 */

/*!
 * \file dev/spi_dip204.c
 * \brief DIP204 SPI LCD driver.
 * The DIP204 LCD can be found on the EVK1100 kit from Atmel.
 * It uses a KS0073 in serial mode.
 * Datasheet: http://www.lcd-module.de/eng/pdf/zubehoer/ks0073.pdf
 *
 * \verbatim
 * $Id: spi_dip204.c,v 1.2 2009/02/06 15:50:06 haraldkipp Exp $
 * \endverbatim
 */

#include <cfg/memory.h>
#include <cfg/arch/gpio.h>

#include <sys/nutconfig.h>
#include <dev/hd44780.h>
#include <dev/term.h>
#include <dev/spibus.h>
#include <sys/timer.h>
#include <sys/nutdebug.h>

#ifndef SPI_RATE_DIP204
#define SPI_RATE_DIP204  1000000
#endif

#ifndef SPI_MODE_DIP204
#define SPI_MODE_DIP204 SPI_MODE_1
#endif

#if !defined(LCD_4x20) && !defined(LCD_4x16)
#if !defined(LCD_2x40) && !defined(LCD_2x20) && !defined(LCD_2x16) && !defined(LCD_2x8)
#if !defined(LCD_1x20) && !defined(LCD_1x16) && !defined(LCD_1x8)
#if !defined(KS0073_CONTROLLER)
#define LCD_2x16
#endif                          /* !KS0073_CONTROLLER */
#endif                          /* !1 line */
#endif                          /* !2 lines */
#endif                          /* !4 lines */

#ifndef LCD_ROWS
#if defined(LCD_4x20) || defined(LCD_4x16) || defined(KS0073_CONTROLLER)
#define LCD_ROWS    4
#elif defined(LCD_1x20) || defined(LCD_1x16) || defined(LCD_1x8)
#define LCD_ROWS    1
#else
#define LCD_ROWS    2
#endif
#endif                          /* LCD_ROWS */

#ifndef LCD_COLS
#if defined(LCD_2x40)
#define LCD_COLS    40
#elif defined(LCD_4x20) || defined(LCD_2x20) || defined(LCD_1x20) || defined(KS0073_CONTROLLER)
#define LCD_COLS    20
#elif defined(LCD_2x8) || defined(LCD_1x8)
#define LCD_COLS    8
#else
#define LCD_COLS    16
#endif
#endif                          /* LCD_COLS */


#if defined(DIP204_BACKLIGHT_PIO_BIT)
#if defined(DIP204_BACKLIGHT_PIO_ID)
#undef GPIO_ID
#define GPIO_ID DIP204_BACKLIGHT_PIO_ID
#include <cfg/arch/porttran.h>
static INLINE void DIP204_BACKLIGHT_LO(void) { GPIO_SET_LO(DIP204_BACKLIGHT_PIO_BIT); }
static INLINE void DIP204_BACKLIGHT_HI(void) { GPIO_SET_HI(DIP204_BACKLIGHT_PIO_BIT); }
static INLINE void DIP204_BACKLIGHT_SO(void) { GPIO_ENABLE(DIP204_BACKLIGHT_PIO_BIT); GPIO_OUTPUT(DIP204_BACKLIGHT_PIO_BIT); }
#endif
#endif

#if !defined(DIP204_BACKLIGHT_PIO_BIT) || !defined(DIP204_BACKLIGHT_PIO_ID)
#define DIP204_BACKLIGHT_LO()
#define DIP204_BACKLIGHT_HI()
#define DIP204_BACKLIGHT_SO()
#endif


/*! Write Command start byte */
#define DIP204_WRITE_COMMAND			0xF8
/*! Write Data start byte */
#define DIP204_WRITE_DATA				0xFA
/*! Read Command start byte, for Busy-flag checking */
#define DIP204_READ_COMMAND				0xFC
/*! Read Data from DDRAM */
#define DIP204_READ_DATA				0xFE
#define DIP204_PERIOD_MAX				50
#define DIP204_CGRAM_BASE_ADDR			0x40

// Forward declare functions
static void LcdInit(NUTDEVICE * dev);
static void LcdSetCursor(uint8_t pos);
static void LcdClear(void);
static void LcdCursorMode(uint8_t on);
static void LcdCursorHome(void);
static void LcdCursorLeft(void);
static void LcdCursorRight(void);
static void LcdWriteInstruction(uint8_t cmd, uint8_t xt);
static void LcdWriteData(uint8_t data);
static void LcdWaitReady(NUTSPINODE * node);

/*!
* \brief DIP204 Display SPI node implementation structure.
*/
NUTSPINODE nodeSpiDip204 = {
	NULL,                       /*!< \brief Pointer to the bus controller driver, node_bus. */
	NULL,                       /*!< \brief Pointer to device driver specific settings, node_stat. */
	SPI_RATE_DIP204,            /*!< \brief Initial clock rate, node_rate. */
	SPI_MODE_DIP204,            /*!< \brief Initial mode, node_mode. */
	8,                          /*!< \brief Initial data bits, node_bits. */
	0                           /*!< \brief Chip select, node_cs. */
};

TERMDCB dcb_term = {
	LcdInit,                    /*!< \brief Initialize display subsystem, dss_init. */
	LcdWriteData,               /*!< \brief Write display character, dss_write. */
	LcdWriteInstruction,        /*!< \brief Write display command, dss_command. */
	LcdClear,                   /*!< \brief Clear display, dss_clear. */
	LcdSetCursor,               /*!< \brief Set display cursor, dss_set_cursor. */
	LcdCursorHome,              /*!< \brief Set display cursor home, dss_cursor_home. */
	LcdCursorLeft,              /*!< \brief Move display cursor left, dss_cursor_left. */
	LcdCursorRight,             /*!< \brief Move display cursor right, dss_cursor_right. */
	LcdCursorMode,              /*!< \brief Switch cursor on/off, dss_cursor_mode. */
	0,                          /*!< \brief Mode flags. */
	0,                          /*!< \brief Status flags. */
	LCD_ROWS,                   /*!< \brief Number of rows. */
	LCD_COLS,                   /*!< \brief Number of columns per row. */
	LCD_COLS,                   /*!< \brief Number of visible columns. */
	0,                          /*!< \brief Cursor row. */
	0,                          /*!< \brief Cursor column. */
	0                           /*!< \brief Display shadow memory. */
};

/*!
* \brief DIP204 LCD device implementation structure.
*/
NUTDEVICE devSpiDip204 = {
	NULL,                       /*!< \brief Pointer to next device, dev_next. */
	{'l', 'c', 'd', 0, 0, 0, 0, 0, 0},    /*!< \brief Unique device name, dev_name. */
	IFTYP_STREAM,                /*!< \brief Type of device, dev_type. */
	0,                          /*!< \brief Base address, dev_base (not used). */
	0,                          /*!< \brief First interrupt number, dev_irq (not used). */
	&nodeSpiDip204,             /*!< \brief Interface control block, dev_icb. */
	&dcb_term,					/*!< \brief Driver control block, dev_dcb. */
	TermInit,					/*!< \brief Driver initialization routine, dev_init. */
	TermIOCtl,					/*!< \brief Driver specific control function, dev_ioctl. */
	0,							/*!< \brief Read from device, dev_read. */
	TermWrite,					/*!< \brief Write to device, dev_write. */
#ifdef __HARVARD_ARCH__
	TermWrite_P,				/*!< \brief Write data from program space to device, dev_write_P. */
#endif
	TermOpen, 			        /*!< \brief Mount volume, dev_open. */
	TermClose,			        /*!< \brief Unmount volume, dev_close. */
	0							/*!< \brief Request file size, dev_size. */
};


/*!
 * \brief Send byte to display.
 *
 * \param node  Specifies the SPI node.
 * \param byte  Data byte to transfer.
 */
static int LcdWriteCommand(NUTSPINODE * node, uint8_t cmd)
{
	int rc = -1;
	NUTSPIBUS *bus;
	unsigned char reverse;
	unsigned char data;

	NUTASSERT(node != NULL);
	bus = (NUTSPIBUS *) node->node_bus;
	NUTASSERT(bus != NULL);
	NUTASSERT(bus->bus_alloc != NULL);
	NUTASSERT(bus->bus_transfer != NULL);
	NUTASSERT(bus->bus_release != NULL);

	switch (cmd)
	{
	case DIP204_READ_COMMAND:
	case DIP204_WRITE_COMMAND:
	case DIP204_READ_DATA:
	case DIP204_WRITE_DATA:
		rc = (*bus->bus_transfer) (node, &cmd, NULL, 1);
		break;
	default:
		/* reverse byte */
		reverse = __byte_swap4(cmd) >> 24;
		data = (reverse & 0xF0);
		/* send D0 to D3 */
		rc = (*bus->bus_transfer) (node, &data, NULL, 1);
		NutDelay(1);
		/* send D4 to D7 */
		data = ((reverse << 4) & 0xF0);
		rc = (*bus->bus_transfer) (node, &data, NULL, 1);
		break;
	}
	NutDelay(1);
	return rc;
}

static void LcdReadByte(NUTSPINODE * node, unsigned char *byte)
{
	NUTSPIBUS *bus = node->node_bus;
	unsigned char reverse = 0x00;
	unsigned char dummy = 0x00;

	/* dummy write */
	(*bus->bus_transfer) (node, &dummy, &reverse, 1);
	/* Revert received byte (issued LSB first by the LCD) */
	*byte = __byte_swap4(reverse) >> 24;
}

static void LcdWaitReady(NUTSPINODE * node)
{
	unsigned char status;

	/* send read command to LCD */
	LcdWriteCommand(node, DIP204_READ_COMMAND);
	/* read next byte */
	do {
		LcdReadByte(node, &status);
		/* keep D7 to know status */
		_NOP();
	} while (status & 0x80);
}

static void LcdInit(NUTDEVICE * dev)
{
	int rc = -1;
	NUTSPINODE *node = dev->dev_icb;
	NUTSPIBUS *bus = node->node_bus;
	DIP204_BACKLIGHT_LO();
	DIP204_BACKLIGHT_SO();
	
	rc = (*bus->bus_alloc) (node, 1000);

	// Send Command Start Byte
	LcdWriteCommand(node, DIP204_WRITE_COMMAND);
	// Send "extended Function Set" Command  (RE=1)
	LcdWriteCommand(node, 0x34);
	// Wait for command execution
	NutSleep(1);
	// Send "Enter 4-Line Mode" Command
	LcdWriteCommand(node, 0x09);
	// Wait for command execution
	NutSleep(1);
	// Send "Function Set" Command (RE=0)
	LcdWriteCommand(node, 0x30);
	// Wait for command execution
	NutSleep(1);
	// Send "Display On Command: Display On, Cursor On, Blink On"
	LcdWriteCommand(node, 0x0F);
	// Wait for command execution
	NutSleep(1);
	// Send "Display Clear" Command
	LcdWriteCommand(node, 0x01);
	// Wait for command execution
	NutSleep(5);
	// Send "Entry Mode Set Command: Increment Mode, Entire Shift off"
	LcdWriteCommand(node, 0x06);
	// Wait for command execution
	NutSleep(1);

	// unselect chip
	rc = (*bus->bus_release) (node);
}

static void LcdSetCursor(uint8_t pos)
{
	NUTSPINODE *node = devSpiDip204.dev_icb;
	NUTSPIBUS *bus = node->node_bus;
	unsigned char column = ((volatile TERMDCB*)(devSpiDip204.dev_dcb))->dcb_col;
	unsigned char line = ((volatile TERMDCB*)(devSpiDip204.dev_dcb))->dcb_row;
	unsigned char address = 0;

	(*bus->bus_alloc) (node, 1000);
	if ((column <= 20) && (line <= 4))
	{
		/* Calculate DDRAM address from line and row values */
		address = ( (line-1) * 32 ) + ( column-1 ) + 128;
	}
	/* Send Command Start Byte */
	LcdWriteCommand(node, DIP204_WRITE_COMMAND);
	/* Send address lower Nibble */
	LcdWriteCommand(node, address);
	LcdWaitReady(node);
	(*bus->bus_release) (node);
}

static void LcdClear(void)
{
	NUTSPINODE *node = devSpiDip204.dev_icb;
	NUTSPIBUS *bus = node->node_bus;

	(*bus->bus_alloc) (node, 1000);
	/* Send Command Start Byte */
	LcdWriteCommand(node, DIP204_WRITE_COMMAND);
	/* Send Display Clear Command */
	LcdWriteCommand(node, 0x01);
	/* Wait for command execution */
	NutSleep(4);
	LcdWaitReady(node);
	(*bus->bus_release) (node);
}

static void LcdCursorMode(uint8_t on)
{
	NUTSPINODE *node = devSpiDip204.dev_icb;
	NUTSPIBUS *bus = node->node_bus;

	(*bus->bus_alloc) (node, 1000);
	LcdWriteCommand(node, DIP204_WRITE_COMMAND);
	if ( on ) {
		/* Send "Display On Command: Display On, Cursor On, Blink On" */
		LcdWriteCommand(node, 0x0F);
	} else {
		/* Send "Display On Command: Display On, Cursor On, Blink On" */
		LcdWriteCommand(node, 0x0C);
	}
	LcdWaitReady(node);
	(*bus->bus_release) (node);
}

static void LcdCursorHome(void)
{
	NUTSPINODE *node = devSpiDip204.dev_icb;
	NUTSPIBUS *bus = node->node_bus;

	(*bus->bus_alloc) (node, 1000);
	LcdWriteCommand(node, DIP204_WRITE_COMMAND);
	LcdWriteCommand(node, 1 << 1);
	LcdWaitReady(node);
	(*bus->bus_release) (node);
}

static void LcdCursorLeft(void)
{
	NUTSPINODE *node = devSpiDip204.dev_icb;
	NUTSPIBUS *bus = node->node_bus;

	(*bus->bus_alloc) (node, 1000);
	LcdWriteCommand(node, DIP204_WRITE_COMMAND);
	LcdWriteCommand(node, 1 << 4);
	LcdWaitReady(node);
	(*bus->bus_release) (node);
}

static void LcdCursorRight(void)
{
	NUTSPINODE *node = devSpiDip204.dev_icb;
	NUTSPIBUS *bus = node->node_bus;

	(*bus->bus_alloc) (node, 1000);
	// Send Command Start Byte
	LcdWriteCommand(node, DIP204_WRITE_COMMAND);
	LcdWriteCommand(node, 1 << 4 | 1 << 2);
	LcdWaitReady(node);
	(*bus->bus_release) (node);
}

static void LcdWriteInstruction(uint8_t cmd, uint8_t xt)
{
	NUTSPINODE *node = devSpiDip204.dev_icb;
	NUTSPIBUS *bus = node->node_bus;

	(*bus->bus_alloc) (node, 1000);
	// Send Command Start Byte
	LcdWriteCommand(node, DIP204_WRITE_COMMAND);
	LcdWriteCommand(node, cmd);
	LcdWaitReady(node);
	(*bus->bus_release) (node);
}

/*!
* \brief Send data byte to LCD controller.
*
* \param data Byte to send.
*/
static void LcdWriteData(uint8_t data)
{
	NUTSPINODE *node = devSpiDip204.dev_icb;
	NUTSPIBUS *bus = node->node_bus;

	(*bus->bus_alloc) (node, 1000);
	LcdWriteCommand(node, DIP204_WRITE_DATA);
	LcdWriteCommand(node, data);
	LcdWaitReady(node);
	(*bus->bus_release) (node);
}

